import os
import subprocess
import javalang
import threading
import shutil
import xml.etree.ElementTree as ET
from core.testAgent.LoggerManager import LoggerManager
from core.testAgent.ProgressTracker import progress_tracker


# Warning: this is not a stable dependency, it may be removed in the future
from core.testAgent.Config import (
    CKGConstruction_Jar_Path,
    JAVA_HOME,
    NEO4J_SERVER_PASSWORD,
    NEO4J_SERVER_URL,
    NEO4J_SERVER_USER,
)


def singleton(cls):
    instances = {}

    def get_instance(*args, **kwargs):
        if cls not in instances:  # 如果实例不存在
            instances[cls] = cls(*args, **kwargs)  # 创建并保存实例
        return instances[cls]

    return get_instance


def check_infor(func):
    def wrapper(self, *args, **kwargs):
        missing = []
        if self.inject_dir is None:
            missing.append("inject_dir")
        if self.test_file_name is None:
            missing.append("test_file_name")
        if self.test_code is None:
            missing.append("test_code")
        if self.base_path is None:
            missing.append("base_path")
        if missing:
            pass # evosuite special
            # raise Exception(f"The following variables must be set before running test: {', '.join(missing)}.")
        return func(self, *args, **kwargs)

    return wrapper


# @singleton
class EnvironmentService:
    def __init__(self):
        """
        初始化环境服务类，用于管理测试环境的相关操作。
        其中参数的设置分为三个部分，base_path是在命令执行目录，对于同一个项目来说，其位置相同，故应该在初始化时设置；
        inject_dir是用于注入测试代码的目录，每次注入的位置不同，故应该在针对同一被测方法的测试前设置；
        test_file_name和test_code是测试代码的文件名和代码内容，故应该在每次测试前设置。
        """
        self._current_path = os.getcwd()
        self.base_path = None
        self.inject_dir = None
        self.test_file_name = None
        self.test_code = None
        self.ready_to_test = False
        self.lock = threading.Lock()  # 创建锁
        self.number = 1
        self.env = os.environ.copy()
        self.maven_cmd = "mvn.cmd" if os.name == "nt" else "mvn"
        logger = LoggerManager().logger
        self._notify(f"[EnvironmentService] Initialize at cwd: {self._current_path}")
        self._notify(f"[EnvironmentService] Configured base_path: {self.base_path}")
        self._notify(f"[JavaEnv:init] Config JAVA_HOME={JAVA_HOME or 'Not set'}, "
                    f"Original env JAVA_HOME={self.env.get('JAVA_HOME') or 'Not set'}")
        java_home = JAVA_HOME or self.env.get("JAVA_HOME")
        if java_home:
            self.env["JAVA_HOME"] = java_home
            java_bin = os.path.join(java_home, "bin")
            current_path = self.env.get("PATH", "")
            self.env["PATH"] = java_bin + os.pathsep + current_path if current_path else java_bin
            self._notify(f"[JavaEnv:init] Using JAVA_HOME={java_home}")
            self._notify(f"[JavaEnv:init] PATH updated (head): {self.env['PATH']}")
        else:
            LoggerManager().logger.warning("JAVA_HOME is not configured. Please set environment.JAVA_HOME in userConfig.json.")
        self.job_id = None
        self.allow_human_assistance = True
        self._log_java_environment("init")

    @property
    def current_path(self):
        return self._current_path

    def _log_java_environment(self, context: str):
        """
        输出当前 Java 相关的环境变量，帮助定位 JAVA_HOME 是否被正确读取。
        """
        java_home = self.env.get("JAVA_HOME") or os.environ.get("JAVA_HOME")
        path_value = self.env.get("PATH") or os.environ.get("PATH", "")
        java_executable = shutil.which("java", path=path_value)
        LoggerManager().logger.info(
            f"[JavaEnv:{context}] JAVA_HOME={java_home or 'Not set'} | java executable={java_executable or 'Not found'}"
        )
        LoggerManager().logger.info(f"[JavaEnv:{context}] PATH={path_value}")
        LoggerManager().logger.info(f"[JavaEnv:{context}] base_path={self.base_path or 'Not set'}")

    def set_base_path(self, base_path):
        self.base_path = base_path

    def set_inject_dir(self, inject_dir):
        self.inject_dir = inject_dir

    def set_job_id(self, job_id):
        self.job_id = job_id

    def set_human_assistance_enabled(self, enabled: bool):
        self.allow_human_assistance = enabled

    def get_inject_dir(self):
        return self.inject_dir

    def _notify(self, message: str):
        print(message)
        if hasattr(self, "job_id"):
            job_id = self.job_id
        else:
            LoggerManager().logger.warning(
                "[EnvironmentService] job_id attribute missing while notifying progress; defaulting to None."
            )
            job_id = None
        progress_tracker.record_message(job_id, message)

    def set_test_infor(self, test_file_name, test_code):
        self.test_file_name = test_file_name
        self.test_code = test_code
        if self.test_file_name.endswith(".java"):
            self.test_file_name = self.test_file_name[:-5]

    def set_test_file_name(self, test_file_name):
        self.test_file_name = test_file_name
        self.ready_to_test = True

    @check_infor
    def syntax_check(self):
        try:
            javalang.parse.parse(self.test_code)
            return True, "Syntax check passed."
        except javalang.parser.JavaSyntaxError as e:
            return False, e

    @check_infor
    def test_code_inject(self):
        inject_path = os.path.join(self.inject_dir, self.test_file_name + ".java")
        if not os.path.exists(self.inject_dir):
            os.makedirs(self.inject_dir)
        try:
            with open(inject_path, "w") as f:
                f.write(self.test_code)
                self.ready_to_test = True
        except Exception:
            raise Exception(f"Failed to inject test code to file: {inject_path}.")

    def total_test_remove(self):
        """
        删除 src/test 目录下的所有测试用例。
        """
        if not self.base_path:
            raise Exception("base_path is not set. Please set it before removing tests.")

        target_dir = os.path.join(self.base_path, "src", "test")
        if os.path.exists(target_dir):
            try:
                subprocess.run("rm -rf src/test/*", shell=True, check=True, cwd=self.base_path)
                self._notify("[✓] 所有测试用例已从 src/test 中移除")
            except subprocess.CalledProcessError as e:
                raise RuntimeError(f"删除测试用例失败：{e}")
        else:
            self._notify("[!] src/test 目录不存在")


    @check_infor
    def test_code_remove(self):
        inject_path = os.path.join(self.inject_dir, self.test_file_name + ".java")
        self.ready_to_test = False
        if os.path.exists(inject_path):
            os.remove(inject_path)
            return True
        return False

    @check_infor
    def compile_test(self):
        if not self.ready_to_test:
            raise Exception("Environment is not ready to run test.")

        LoggerManager().logger.info(f"=============== START COMPILE TEST ===============")
        # self._notify("START COMPILE TEST")
        compile_command = [self.maven_cmd, "clean", "test-compile", f"-Dtest={self.test_file_name}"]
        LoggerManager().logger.info(f"execute command: {' '.join(compile_command)}")
        # self._notify(f"execute command: {' '.join(compile_command)}")

        result = subprocess.run(compile_command, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE, text=True, cwd=self.base_path)

        compile_output = result.stdout
        if "BUILD SUCCESS" in compile_output:
            return True, compile_output
        elif "BUILD FAILURE" in compile_output:
            return False, compile_output
        else:
            raise Exception("can not find result in compile output.")

    @check_infor
    def execute_test(self):
        if not self.ready_to_test:
            raise Exception("Environment is not ready to run test.")

        self._log_java_environment("execute_test")
        LoggerManager().logger.info(f"=============== START EXECUTE TEST ===============")
        # self._notify("START EXECUTE TEST")
        test_command = [self.maven_cmd, "clean", "test", f"-Dtest={self.test_file_name}"]
        LoggerManager().logger.info(f"execute command: {' '.join(test_command)}")
        # self._notify(f"execute command: {' '.join(test_command)}")

        try:
            # 设置timeout为20秒
            result = subprocess.run(test_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path, timeout=30, env=self.env)

            test_output = result.stdout
            # self._notify(f"execute stdout:{test_output}")
            if result.stderr:
                self._notify(f"execute stderr:{result.stderr}")
            if "BUILD SUCCESS" in test_output:
                return True, test_output
            elif "BUILD FAILURE" in test_output:
                return False, test_output
            else:
                raise Exception("can not find result in execute output.")

        except subprocess.TimeoutExpired as e:
            # 超时处理
            return False, f"Time out: The process exceeded the 20-second limit. Command: {' '.join(test_command)}"

        except Exception as e:
            # 其他异常
            return False, f"Error: {str(e)}"

    @check_infor
    def total_execute_test(self):
        if not self.ready_to_test:
            raise Exception("Environment is not ready to run test.")

        self._log_java_environment("total_execute_test")
        LoggerManager().logger.info(f"=============== START EXECUTE TEST ===============")
        self._notify("START EXECUTE TEST (total)")
        test_command = [self.maven_cmd, "clean", "test"]
        LoggerManager().logger.info(f"execute command: {' '.join(test_command)}")
        # self._notify(f"execute command: {' '.join(test_command)}")

        try:
            # 设置timeout为20秒
            result = subprocess.run(test_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path, timeout=20)

            test_output = result.stdout
            # self._notify(f"total execute stdout:{test_output}")
            if result.stderr:
                self._notify(f"total execute stderr:{result.stderr}")
            if "BUILD SUCCESS" in test_output:
                return True, test_output
            elif "BUILD FAILURE" in test_output:
                return False, test_output
            else:
                raise Exception("can not find result in execute output.")

        except subprocess.TimeoutExpired as e:
            # 超时处理
            return False, f"Time out: The process exceeded the 20-second limit. Command: {' '.join(test_command)}"

        except Exception as e:
            # 其他异常
            return False, f"Error: {str(e)}"

    @check_infor
    def dependency_generate(self):
        LoggerManager().logger.info(f"=============== START DEPENDENCY GENERATE ===============")
        self._notify("START DEPENDENCY GENERATE")
        dependency_command = [self.maven_cmd, "dependency:copy-dependencies"]
        LoggerManager().logger.info(f"execute command: {' '.join(dependency_command)}")
        # self._notify(f"execute command: {' '.join(dependency_command)}")

        result = subprocess.run(dependency_command, stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE, text=True, cwd=self.base_path)
        test_output = result.stdout
        # self._notify(f"==========stdout output:{result.stdout}")
        if "BUILD SUCCESS" in test_output:
            return True, test_output
        elif "BUILD FAILURE" in test_output:
            return False, test_output
        else:
            raise Exception("can not find result in execute output.")

    def run_compile_test(self, test_code: str, test_file_name: str):
        with self.lock:
            self.set_test_infor(test_file_name, test_code)
            syntax_result, syntax_output = self.syntax_check()
            if not syntax_result:
                return {"result": "Syntax Error", "output": syntax_output}

            self.test_code_inject()
            compile_result, compile_output = self.compile_test()
            # self._notify(f"compile_output:{compile_output}")
            if not compile_result:
                self.test_code_remove()
                return {"result": "Compile Error", "output": compile_output}
            self.test_code_remove()
            return {"result": "Success", "output": compile_output}

    def run_execute_test(self, test_code: str, test_file_name: str):
        with self.lock:
            self.set_test_infor(test_file_name, test_code)
            self.test_code_inject()
            execute_result, execute_output = self.execute_test()
            # self._notify(f"execute_output:{execute_output}")
            if not execute_result:
                self.test_code_remove()
                return {"result": "Execute Error", "output": execute_output}
            self.test_code_remove()
            return {"result": "Success", "output": execute_output}

    def run_compile_and_execute_test(self, test_code: str, test_file_name: str):
        with self.lock:
            self.set_test_infor(test_file_name, test_code)
            syntax_result, syntax_output = self.syntax_check()
            if not syntax_result:
                return {"result": "Syntax Error", "output": syntax_output}

            self.test_code_inject()
            compile_result, compile_output = self.compile_test()
            # self._notify(f"compile_output:{compile_output}")
            if not compile_result:
                self.test_code_remove()
                return {"result": "Compile Error", "output": compile_output}
            execute_result, execute_output = self.execute_test()
            # self._notify(f"execute_output:{execute_output}")
            if not execute_result:
                self.test_code_remove()
                return {"result": "Execute Error", "output": execute_output}
            self.test_code_remove()
            return {"result": "Success", "output": execute_output}

    def simple_run_coverage_test(self, package_name, class_name, focal_method_signature: str, start_line: int, end_line: int):
        with self.lock:
            self._log_java_environment("simple_run_coverage_test")
            self.total_execute_test()
            exec_file = os.path.join(self.base_path, "target", "jacoco.exec")
            if not os.path.exists(exec_file):
                LoggerManager().logger.warning(
                    f"[Coverage] Jacoco exec file missing at {exec_file}. target_dir_exists={os.path.exists(os.path.dirname(exec_file))}, base_path={self.base_path}"
                )
                self.test_code_remove()
                return {"result": "Error", "output": "Jacoco exec file not found."}

            LoggerManager().logger.info(f"=============== START COVERAGE CALCULATE ===============")
            # print("=============== START COVERAGE CALCULATE ===============")
            coverage_command = [self.maven_cmd, "jacoco:report"]
            LoggerManager().logger.info(f"execute command: {' '.join(coverage_command)}")
            # print(f"execute command: {' '.join(coverage_command)}")

            result = subprocess.run(coverage_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path)

            coverage_output = result.stdout
            self.test_code_remove()
            if "BUILD SUCCESS" in coverage_output:
                return self.parse_jacoco_report(package_name, class_name, focal_method_signature, start_line, end_line)
            elif "BUILD FAILURE" in coverage_output:
                return {"result": "Error", "output": coverage_output}
            else:
                raise Exception("can not find result in compile output.")

    def direct_run_coverage_test(self, test_file_name: str, package_name, class_name,
                          focal_method_signature: str, boundary_list: list):
        with self.lock:
            self._log_java_environment("direct_run_coverage_test")
            self.set_test_file_name(test_file_name)
            self.execute_test()
            exec_file = os.path.join(self.base_path, "target", "jacoco.exec")
            if not os.path.exists(exec_file):
                LoggerManager().logger.warning(
                    f"[Coverage] Jacoco exec file missing at {exec_file}. target_dir_exists={os.path.exists(os.path.dirname(exec_file))}, base_path={self.base_path}"
                )
                # self.test_code_remove()
                return [{"result": "Error", "output": "Jacoco exec file not found."}]

            LoggerManager().logger.info(f"=============== START COVERAGE CALCULATE ===============")
            # print("=============== START COVERAGE CALCULATE ===============")
            coverage_command = [self.maven_cmd, "jacoco:report"]
            LoggerManager().logger.info(f"execute command: {' '.join(coverage_command)}")
            # print(f"execute command: {' '.join(coverage_command)}")

            result = subprocess.run(coverage_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path, env=self.env)

            coverage_output = result.stdout
            # self.test_code_remove()
            if "BUILD SUCCESS" in coverage_output:
                return [self.parse_jacoco_report(package_name, class_name, "", start_line, end_line) for start_line, end_line in boundary_list]
            elif "BUILD FAILURE" in coverage_output:
                return [{"result": "Error", "output": coverage_output}]
            else:
                raise Exception("can not find result in compile output.")

    def run_coverage_test(self, test_code: str, test_file_name: str, package_name, class_name,
                          focal_method_signature: str, start_line: int, end_line: int):
        with self.lock:
            self._log_java_environment("run_coverage_test")
            self.set_test_infor(test_file_name, test_code)
            self.test_code_inject()
            self.execute_test()
            exec_file = os.path.join(self.base_path, "target", "jacoco.exec")
            if not os.path.exists(exec_file):
                LoggerManager().logger.warning(
                    f"[Coverage] Jacoco exec file missing at {exec_file}. target_dir_exists={os.path.exists(os.path.dirname(exec_file))}, base_path={self.base_path}"
                )
                self.test_code_remove()
                return {"result": "Error", "output": "Jacoco exec file not found."}

            LoggerManager().logger.info(f"=============== START COVERAGE CALCULATE ===============")
            # print("=============== START COVERAGE CALCULATE ===============")
            coverage_command = [self.maven_cmd, "jacoco:report"]
            LoggerManager().logger.info(f"execute command: {' '.join(coverage_command)}")
            # print(f"execute command: {' '.join(coverage_command)}")

            result = subprocess.run(coverage_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path, env=self.env)

            coverage_output = result.stdout
            self.test_code_remove()
            # self._notify(f"run_coverage_test coverage_output:{coverage_output}")
            if "BUILD SUCCESS" in coverage_output:
                return self.parse_jacoco_report(package_name, class_name, focal_method_signature, start_line, end_line)
            elif "BUILD FAILURE" in coverage_output:
                return {"result": "Error", "output": coverage_output}
            else:
                raise Exception("can not find result in compile output.")

    def parse_jacoco_report(self, package_name, class_name, focal_method_signature: str, start_line: int,
                            end_line: int):
        """
        解析 JaCoCo XML 报告，获取指定方法的行覆盖率和分支覆盖率，并从详细覆盖信息中提取具体的覆盖行数据。

        :param package_name: Java 包名, "org.apache.commons.cli"
        :param class_name: Java 类名, "org.apache.commons.cli.OptionValidator"
        :param focal_method_signature: 目标方法名, "validate"
        :param start_line: 目标方法的起始行号, 60
        :param end_line: 目标方法的结束行号, 83
        :return: 字典 { "line_coverage": float, "branch_coverage": float, "covered_lines": list, "missed_lines": list }
        """
        xml_path = os.path.join(self.base_path, "target", "site", "jacoco", "jacoco.xml")
        # self._notify(f"parse_jacoco_report xml_path:{xml_path}")
        if not os.path.exists(xml_path):
            LoggerManager().logger.warning(
                f"[Coverage] Jacoco XML report missing at {xml_path}. base_path={self.base_path}"
            )
            return {"result": "Error", "output": "Jacoco XML report not found."}
        tree = ET.parse(xml_path)
        root = tree.getroot()
        package_name = package_name.replace(".", "/")
        class_name = class_name.replace(".", "/")
        # 先找到 class 对应的 sourcefilename
        source_file_name = None

        # 在 sourcefile 部分查找详细的行覆盖信息
        covered_lines = set()
        missed_lines = set()
        total_lines, covered_lines_count = 0, 0
        total_branches, covered_branches_count = 0, 0

        for package in root.findall("package"):
            if package.get("name") != package_name:
                continue  # 只查找匹配的 package

            for class_element in package.findall("class"):
                if class_element.get("name") == class_name or class_element.get("name").endswith("/" + class_name):
                    source_file_name = class_element.get("sourcefilename")
                    break

            if not source_file_name:
                return {"result": "Error", "output": "Source file not found."}

            for source_file in package.findall("sourcefile"):
                if source_file.get("name") != source_file_name:
                    continue  # 只查找匹配的 sourcefile

                for line in source_file.findall("line"):
                    line_number = int(line.get("nr"))
                    if not (start_line <= line_number <= end_line):
                        continue  # 过滤掉不在方法范围内的行

                    missed_instr = int(line.get("mi", "0"))  # missed instructions
                    covered_instr = int(line.get("ci", "0"))  # covered instructions
                    missed_branches = int(line.get("mb", "0"))  # missed branches
                    covered_branches = int(line.get("cb", "0"))  # covered branches

                    # 记录覆盖行和未覆盖行
                    if missed_instr == 0 and covered_instr > 0:
                        covered_lines.add(line_number)
                    else:
                        missed_lines.add(line_number)

                    # 计算行覆盖率
                    covered_lines_count += covered_instr > 0
                    total_lines += 1

                    # 计算分支覆盖率
                    covered_branches_count += covered_branches
                    total_branches += missed_branches + covered_branches

        # 计算最终覆盖率
        line_coverage = (covered_lines_count / total_lines) * 100 if total_lines > 0 else 0
        branch_coverage = (covered_branches_count / total_branches) * 100 if total_branches > 0 else line_coverage  # 无BRANCH时用LINE替代
        # self._notify(f"{focal_method_signature}: Line Coverage: {line_coverage}%, Branch Coverage: {branch_coverage}%")
        return {
            "result": "Success",
            "output": {
                "function_name": focal_method_signature,
                "line_coverage": line_coverage,
                "branch_coverage": branch_coverage,
                "covered_lines": sorted(list(covered_lines)),
                "missed_lines": sorted(list(missed_lines))
            }
        }

    def simple_run_mutation_test(self, package_name, class_name, start_line, end_line):
        with self.lock:
            self._log_java_environment("simple_run_mutation_test")
            self.total_execute_test()
            LoggerManager().logger.info(f"=============== START MUTATION CALCULATE ===============")
            mutation_command = [self.maven_cmd, "org.pitest:pitest-maven:mutationCoverage",
                                f"-DtargetClasses={class_name}"]
            LoggerManager().logger.info(f"execute command: {' '.join(mutation_command)}")

            result = subprocess.run(mutation_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path)

            mutation_output = result.stdout
            # self.test_code_remove()
            if "BUILD SUCCESS" in mutation_output:
                return self.parse_pitest_report(start_line, end_line)
            elif "BUILD FAILURE" in mutation_output:
                return {"result": "Error", "output": mutation_output}
            else:
                raise Exception("can not find result in mutation output.")

    def direct_run_mutation_test(self, package_name: str, focal_class_name: str, test_file_name: str, boundary_list: list):
        # "mvn org.pitest:pitest-maven:mutationCoverage -DtargetClasses=org.apache.commons.cli.Option -DtargetTests=org.apache.commons.cli.OptionTest"
        with self.lock:
            self._log_java_environment("direct_run_mutation_test")
            self.set_test_file_name(test_file_name)
            execute_result, execute_output = self.execute_test()
            if not execute_result:
                # self.test_code_remove()
                return [{"result": "Error", "output": "Compilation or execution error. Environment is not ready to run mutation test."}]
            class_name = package_name + "." + focal_class_name
            test_name = package_name + "." + self.test_file_name
            LoggerManager().logger.info(f"=============== START MUTATION TEST ===============")
            # print("=============== START MUTATION TEST ===============")
            mutation_command = [self.maven_cmd, "org.pitest:pitest-maven:mutationCoverage",
                                f"-DtargetClasses={class_name}",
                                f"-DtargetTests={test_name}"]
            LoggerManager().logger.info(f"execute command: {' '.join(mutation_command)}")
            # print(f"execute command: {' '.join(mutation_command)}")

            result = subprocess.run(mutation_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path, env=self.env)

            mutation_output = result.stdout
            # self.test_code_remove()
            if "BUILD SUCCESS" in mutation_output:
                return [self.parse_pitest_report(start_line, end_line) for start_line, end_line in boundary_list]
            elif "BUILD FAILURE" in mutation_output:
                return [{"result": "Error", "output": mutation_output}]
            else:
                raise Exception("can not find result in mutation output.")

    def run_mutation_test(self, package_name: str, focal_class_name: str, test_code: str, test_file_name: str, start_line: int, end_line: int):
        # "mvn org.pitest:pitest-maven:mutationCoverage -DtargetClasses=org.apache.commons.cli.Option -DtargetTests=org.apache.commons.cli.OptionTest"
        with self.lock:
            self._log_java_environment("run_mutation_test")
            self.set_test_infor(test_file_name, test_code)
            self.test_code_inject()
            execute_result, execute_output = self.execute_test()
            if not execute_result:
                self.test_code_remove()
                return {"result": "Error", "output": "Compilation or execution error. Environment is not ready to run mutation test."}
            class_name = package_name + "." + focal_class_name
            test_name = package_name + "." + self.test_file_name
            LoggerManager().logger.info(f"=============== START MUTATION TEST ===============")
            # print("=============== START MUTATION TEST ===============")
            mutation_command = [self.maven_cmd, "org.pitest:pitest-maven:mutationCoverage",
                                f"-DtargetClasses={class_name}",
                                f"-DtargetTests={test_name}"]
            LoggerManager().logger.info(f"execute command: {' '.join(mutation_command)}")
            # print(f"execute command: {' '.join(mutation_command)}")

            result = subprocess.run(mutation_command, stdout=subprocess.PIPE,
                                    stderr=subprocess.PIPE, text=True, cwd=self.base_path, env=self.env)

            mutation_output = result.stdout
            self.test_code_remove()
            if "BUILD SUCCESS" in mutation_output:
                return self.parse_pitest_report(start_line, end_line)
            elif "BUILD FAILURE" in mutation_output:
                return {"result": "Error", "output": mutation_output}
            else:
                raise Exception("can not find result in mutation output.")

    def parse_pitest_report(self, start_line: int, end_line: int):
        """
            解析 PITest XML 报告，并筛选指定行号范围内的变异体
            参数：
              - xml_path (str): PITest 生成的 mutations.xml 报告路径
              - start_line (int): 被测函数的起始行号
              - end_line (int): 被测函数的结束行号
            返回：
              - mutation_score (float): 变异得分
              - filtered_mutations (list): 变异体的详细信息
            """
        xml_path = os.path.join(self.base_path, "target", "pit-reports", "mutations.xml")
        if not os.path.exists(xml_path):
            LoggerManager().logger.warning(
                f"[Mutation] PITest report missing at {xml_path}. base_path={self.base_path}"
            )
            return {"result": "Error", "output": "Jacoco XML report not found."}
        tree = ET.parse(xml_path)
        root = tree.getroot()

        total_mutations = 0
        survived_mutants = 0
        filtered_mutations = []

        for mutation in root.findall(".//mutation"):
            # 提取变异信息
            line_number = int(mutation.find("lineNumber").text)
            if not (start_line <= line_number <= end_line):
                continue  # 只保留指定行号范围的变异

            total_mutations += 1
            status = mutation.get("status")
            class_name = mutation.find("mutatedClass").text
            method_name = mutation.find("mutatedMethod").text
            description = mutation.find("description").text
            mutator = mutation.find("mutator").text
            num_tests = mutation.get("numberOfTestsRun")
            killing_test = mutation.find("killingTest").text if mutation.find("killingTest") is not None else "None"

            # 统计未被测试杀死的变异 (SURVIVED)
            if status != "KILLED":
                survived_mutants += 1

            # 存储变异体详细信息
            filtered_mutations.append({
                "Class": class_name,
                "Method": method_name,
                "Line": line_number,
                "Mutator": mutator,
                "Description": description,
                "Status": status,
                "Tests Run": num_tests,
                "Killing Test": killing_test
            })

        # 计算变异得分
        killed_mutants = total_mutations - survived_mutants
        mutation_score = (killed_mutants / total_mutations) * 100 if total_mutations > 0 else 0

        return {
            "result": "Success",
            "output": {
                "mutation_score": mutation_score,
                "filtered_mutations": filtered_mutations
            }
        }

    def simple_fix(self, test_code):
        if "package" in test_code:
            test_code = test_code.replace("package", "// package")
        # 规范化路径，兼容 Windows 盘符和分隔符
        normalized_dir = os.path.normpath(self.inject_dir)
        _, path_no_drive = os.path.splitdrive(normalized_dir)
        path_slash = path_no_drive.replace(os.sep, "/")

        marker = "src/test/java" if "src/test/java" in path_slash else "src/test"
        if marker in path_slash:
            package_path = path_slash.split(marker, 1)[-1].lstrip("/\\")
        else:
            # 如果路径中没有测试目录标记，退回用去掉盘符的相对路径
            package_path = path_slash.lstrip("/\\")

        package_name = (
            package_path
            .strip("/\\.")
            .replace("/", ".")
            .replace("\\", ".")
        )
        test_code = "package " + package_name + ";\n\n" + test_code
        return test_code

    def add_test_to_CKG(self, test_code: str, test_file_name: str):
        with self.lock:
            self.set_test_infor(test_file_name, test_code)
            self.test_code_inject()
            self.compile_test()
            self.dependency_generate()
            jar_path = CKGConstruction_Jar_Path
            if not jar_path:
                raise RuntimeError(
                    "CKGConstruction jar path is missing from the extension bundle."
                )
            neo4j_url = NEO4J_SERVER_URL or "bolt://localhost:7687"
            neo4j_user = NEO4J_SERVER_USER or "neo4j"
            neo4j_password = NEO4J_SERVER_PASSWORD or "123456"
            cmd = ["java", "-jar", jar_path,
                   "-p", self.inject_dir,
                   "-u", neo4j_url,
                   "-n", neo4j_user,
                   "-w", neo4j_password,
                   "-t",
                   "-c"]
            result = subprocess.run(cmd, capture_output=True, text=True)
            self.test_code_remove()
            if result.returncode == 0:
                return {"result": "Success", "output": result.stdout}
            else:
                return {"result": "Error", "output": result.stderr}

####################### EVOSUITE #######################
    def generate_by_evosuite(self, focal_clazz_fq_name):
        """
            使用 EvoSuite 为指定类生成测试用例。

            参数:
                focal_clazz_fq_name (str): 被测类的全限定名，如 "org.apache.commons.lang3.ArrayUtils"
            """

        # EvoSuite 配置（按需修改）
        evosuite_jar = "/Users/mac/Desktop/TestAgents/evosuite-1.2.0.jar"
        project_cp = "target/classes"  # 被测 class 文件路径
        search_budget = 60  # 生成时间（秒）

        self._notify(f"[*] Generating tests for class: {focal_clazz_fq_name}")

        cmd = [
            "java", "-jar", evosuite_jar,
            "-class", focal_clazz_fq_name,
            "-projectCP", project_cp
        ]

        try:
            result = subprocess.run(cmd, capture_output=True, text=True, cwd=self.base_path)
            # self._notify(result.stdout)
            if result.returncode != 0:
                self._notify(f"[!] EvoSuite exited with errors:\n{result.stderr}")
            else:
                self._notify("[+] Test generation finished.")
        except Exception as e:
            self._notify(f"[!] Failed to run EvoSuite: {e}")

    def clear_test_files(self):
        """
        使用 shell 命令清空 src/test/java 并运行 mvn clean compile。
        若编译失败则抛出异常。
        """
        try:
            # 清空 src/test/java 下所有内容
            subprocess.run("rm -rf src/test/java/*", shell=True, check=True, cwd=self.base_path)
            self._notify("[✓] 清空 src/test/java 成功")

            # 执行 mvn clean compile
            self._notify("[*] 执行 mvn clean compile")
            mvn_clean_compile = f"{self.maven_cmd} clean compile"
            subprocess.run(mvn_clean_compile, shell=True, check=True, cwd=self.base_path)
            self._notify("[✓] Maven 编译成功 ✅")

        except subprocess.CalledProcessError as e:
            raise RuntimeError(f"[✗] Maven 编译失败 ❌\n命令: {e.cmd}\n退出码: {e.returncode}")

    def move_test_file(self):
        """
        将 evosuite-tests 下的所有文件复制到 src/test/java 目录（保留包路径结构）。
        """
        cmd = "cp -r evosuite-tests/* src/test/java/"
        try:
            subprocess.run(cmd, shell=True, check=True, cwd=self.base_path)
            self._notify("[✓] 测试文件已成功移动到 src/test/java/")
        except subprocess.CalledProcessError as e:
            self._notify(f"[!] 移动测试文件失败：{e}")

    def run_evosuite_test_case(self):
        pass
